﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Web.Mvc;
using Nb.Common.Caching;
using Nb.Common.Ioc;

namespace Nb.Common.Web
{
    public abstract class NbViewBagBase<T> : DynamicObject where T : NbViewBagBase<T>, new()
    {
        public static dynamic Current
        {
            get
            {
                var fullName = typeof(T).FullName;
                var current = NbViewBagData.Current[fullName];
                if (current == null)
                {
                    current = new T();
                    NbViewBagData.Current[fullName] = current;
                }
                return current;
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return NbViewBagData.Current.TryGetMember(binder, out result);
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            return NbViewBagData.Current.TrySetMember(binder, value);
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return NbViewBagData.Current.GetDynamicMemberNames();
        }
    }

    public sealed class NbViewBag : NbViewBagBase<NbViewBag>
    {
    }

    public sealed class NbViewBagData
    {
        private ViewDataDictionary _viewDataDictionary = new ViewDataDictionary();
        private NbDynamiPageDataDictionary _dynamicData;
        private dynamic _nbViewBagContainer
        {
            get
            {
                if (_dynamicData == null)
                {
                    _dynamicData = new NbDynamiPageDataDictionary(() => _viewDataDictionary);
                }
                return _dynamicData;
            }
        }

        public static dynamic Current
        {
            get
            {
                var cacheKey = "NbViewBagData";
                var cache =  CoreServiceProvider.LocateService<IPerRequestCacheManager>();
                var nbPageContext = cache.Get<NbViewBagData>(cacheKey);
                if (nbPageContext == null)
                {
                    nbPageContext = new NbViewBagData();
                    cache.Set(cacheKey, nbPageContext);
                }

                return nbPageContext._nbViewBagContainer;
            }
        }
    }

    internal sealed class NbDynamiPageDataDictionary : DynamicObject
    {
        private readonly Func<ViewDataDictionary> _viewDataThunk;

        public NbDynamiPageDataDictionary(Func<ViewDataDictionary> viewDataThunk)
        {
            _viewDataThunk = viewDataThunk;
        }


        public object this[string key]
        {
            get { return _viewDataThunk()[key]; }
            set { _viewDataThunk()[key] = value; }
        }

        private ViewDataDictionary ViewData
        {
            get
            {
                ViewDataDictionary viewData = _viewDataThunk();
                Debug.Assert(viewData != null);
                return viewData;
            }
        }

        // Implementing this function improves the debugging experience as it provides the debugger with the list of all
        // the properties currently defined on the object
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return ViewData.Keys;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = ViewData[binder.Name];
            // since ViewDataDictionary always returns a result even if the key does not exist, always return true
            return true;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            ViewData[binder.Name] = value;
            // you can always set a key in the dictionary so return true
            return true;
        }
    }
}
